/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at 
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  
 */

using System;
using java = biz.ritter.javapi;
using org.apache.commons.collections;
using org.apache.commons.collections.functors;

namespace org.apache.commons.collections.map
{
    /**
     * Decorates another <code>Map</code> returning a default value if the map
     * does not contain the requested key.
     * <p>
     * When the {@link #get(Object)} method is called with a key that does not
     * exist in the map, this map will return the default value specified in
     * the constructor/factory. Only the get method is altered, so the
     * {@link Map#containsKey(Object)} can be used to determine if a key really
     * is in the map or not.
     * <p>
     * The defaulted value is not added to the map.
     * Compare this behaviour with {@link LazyMap}, which does add the value
     * to the map (via a Transformer).
     * <p>
     * For instance:
     * <pre>
     * Map map = new DefaultedMap("NULL");
     * Object obj = map.get("Surname");
     * // obj == "NULL"
     * </pre>
     * After the above code is executed the map is still empty.
     * <p>
     * <strong>Note that DefaultedMap is not synchronized and is not thread-safe.</strong>
     * If you wish to use this map from multiple threads concurrently, you must use
     * appropriate synchronization. The simplest approach is to wrap this map
     * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 
     * exceptions when accessed by concurrent threads without synchronization.
     *
     * @since Commons Collections 3.2
     * @version $Revision$ $Date$
     * 
     * @author Stephen Colebourne
     * @author Rafael U.C. Afonso
     * @see LazyMap
     */
    [Serializable]
    public class DefaultedMap
            : AbstractMapDecorator
            , java.util.Map<Object, Object>, java.io.Serializable
    {

        /** Serialization version */
        private static readonly long serialVersionUID = 19698628745827L;

        /** The transformer to use if the map does not contain a key */
        protected readonly Object value;

        //-----------------------------------------------------------------------
        /**
         * Factory method to create a defaulting map.
         * <p>
         * The value specified is returned when a missing key is found.
         * 
         * @param map  the map to decorate, must not be null
         * @param defaultValue  the default value to return when the key is not found
         * @throws IllegalArgumentException if map is null
         */
        public static java.util.Map<Object, Object> decorate(java.util.Map<Object, Object> map, Object defaultValue)
        {
            if (defaultValue is Transformer)
            {
                defaultValue = ConstantTransformer.getInstance(defaultValue);
            }
            return new DefaultedMap(map, defaultValue);
        }

        /**
         * Factory method to create a defaulting map.
         * <p>
         * The factory specified is called when a missing key is found.
         * The result will be returned as the result of the map get(key) method.
         * 
         * @param map  the map to decorate, must not be null
         * @param factory  the factory to use, must not be null
         * @throws IllegalArgumentException if map or factory is null
         */
        public static java.util.Map<Object, Object> decorate(java.util.Map<Object, Object> map, Factory factory)
        {
            if (factory == null)
            {
                throw new java.lang.IllegalArgumentException("Factory must not be null");
            }
            return new DefaultedMap(map, FactoryTransformer.getInstance(factory));
        }

        /**
         * Factory method to create a defaulting map.
         * <p>
         * The transformer specified is called when a missing key is found.
         * The key is passed to the transformer as the input, and the result
         * will be returned as the result of the map get(key) method.
         * 
         * @param map  the map to decorate, must not be null
         * @param factory  the factory to use, must not be null
         * @throws IllegalArgumentException if map or factory is null
         */
        public static java.util.Map<Object, Object> decorate(java.util.Map<Object, Object> map, Transformer factory)
        {
            if (factory == null)
            {
                throw new java.lang.IllegalArgumentException("Transformer must not be null");
            }
            return new DefaultedMap(map, factory);
        }

        //-----------------------------------------------------------------------
        /**
         * Constructs a new empty <code>DefaultedMap</code> that decorates
         * a <code>HashMap</code>.
         * <p>
         * The object passed in will be returned by the map whenever an
         * unknown key is requested.
         * 
         * @param defaultValue  the default value to return when the key is not found
         */
        public DefaultedMap(Object defaultValue)
            : base(new java.util.HashMap<Object, Object>())
        {
            if (defaultValue is Transformer)
            {
                defaultValue = ConstantTransformer.getInstance(defaultValue);
            }
            this.value = defaultValue;
        }

        /**
         * Constructor that wraps (not copies).
         * 
         * @param map  the map to decorate, must not be null
         * @param value  the value to use
         * @throws IllegalArgumentException if map or transformer is null
         */
        protected DefaultedMap(java.util.Map<Object, Object> map, Object value)
            : base(map)
        {
            this.value = value;
        }

        //-----------------------------------------------------------------------
        /**
         * Write the map out using a custom routine.
         * 
         * @param out  the output stream
         * @throws IOException
         */
        private void writeObject(java.io.ObjectOutputStream outJ)
        {// throws IOException {
            outJ.defaultWriteObject();
            outJ.writeObject(map);
        }

        /**
         * Read the map in using a custom routine.
         * 
         * @param in  the input stream
         * @throws IOException
         * @throws ClassNotFoundException
         */
        private void readObject(java.io.ObjectInputStream inJ)
        {// throws IOException, ClassNotFoundException {
            inJ.defaultReadObject();
            map = (java.util.Map<Object, Object>)inJ.readObject();
        }

        //-----------------------------------------------------------------------
        public override Object get(Object key)
        {
            // create value for key if key is not currently in the map
            if (map.containsKey(key) == false)
            {
                if (value is Transformer)
                {
                    return ((Transformer)value).transform(key);
                }
                return value;
            }
            return map.get(key);
        }

        // no need to wrap keySet, entrySet or values as they are views of
        // existing map entries - you can't do a map-style get on them.
    }
}